#region ... Copyright Notice ...
/*
   Copyright 2006-2008 Provo Labs    http://www.provolabs.com

	Author: Tyler Jensen    http://www.tsjensen.com/blog
	Released by the author with written permission of Provo Labs

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Text;

namespace Atrax.Html
{
	/// <summary>
	/// The concept of the atom comes from Firefox code. It should be noted that there is no effort to create a direct <br/>
	/// one to one relationship with the Firefox code, but rather to simply use the code as basis for the HtmlParser namespace in this library.
	/// </summary>
	public class Atom
	{
		public DtdElement Element;
		/// <summary>
		/// This bool flag reflects the second item in the - O, - -, and O O start/end tag indicators in the DTD elements.
		/// We can ignore the first one because if the tag is there, it's there. Except where a tag is O O and a child
		/// tag can exist--only known case is the TBODY and TR where TR can exist in in a TABLE because the TBODY does not
		/// require an opening tag but TABLE requires that at least one TBODY occur in the TABLE even if it is inferred.
		/// </summary>
		public bool EndTagRequired;
		/// <summary>
		/// Used to determine what elements this atom is allowed to contain. Any other atom or a closing tag
		/// with the same element name will close the indent level of the parser. if no children allowed, this 
		/// tag is EMPTY in accordance with the DTD and treated as an element that has no end tag and may not 
		/// contain content.
		/// </summary>
		public List<DtdElement> AllowedContent;
		public Atom(DtdElement element, bool endTagRequired, params DtdElement[] allowedContent)
		{
			this.Element = element;
			this.EndTagRequired = endTagRequired;
			this.AllowedContent = new List<DtdElement>();
			if(allowedContent != null && allowedContent.Length > 0)
				this.AllowedContent.AddRange(allowedContent);
		}
	}

	/// <summary>
	/// Provides static access to list of Atoms for processing parser logic.
	/// </summary>
	public static class HtmlAtoms
	{
		static HtmlAtoms()
		{
			#region Intialize Combined Element Arrays
			eInline = new DtdElement[1 + eFontStyle.Length + ePhrase.Length + eSpecial.Length + eFormCtrl.Length];
			eInline[0] = DtdElement.PCDATA;
			eFontStyle.CopyTo(eInline, 1);
			ePhrase.CopyTo(eInline, 1 + eFontStyle.Length);
			eSpecial.CopyTo(eInline, 1 + eFontStyle.Length + ePhrase.Length);
			eFormCtrl.CopyTo(eInline, 1 + eFontStyle.Length + ePhrase.Length + eSpecial.Length);

			eBlock = new DtdElement[14 + eHeading.Length + eList.Length]; //14 is PRE
			eBlock[0] = DtdElement.P;
			eBlock[1] = DtdElement.DL;
			eBlock[2] = DtdElement.DIV;
			eBlock[3] = DtdElement.CENTER;
			eBlock[4] = DtdElement.NOSCRIPT;
			eBlock[5] = DtdElement.NOFRAMES;
			eBlock[6] = DtdElement.BLOCKQUOTE;
			eBlock[7] = DtdElement.FORM;
			eBlock[8] = DtdElement.ISINDEX;
			eBlock[9] = DtdElement.HR;
			eBlock[10] = DtdElement.TABLE;
			eBlock[11] = DtdElement.FIELDSET;
			eBlock[12] = DtdElement.ADDRESS;
			eBlock[13] = DtdElement.PRE;
			eHeading.CopyTo(eBlock, 14);
			eList.CopyTo(eBlock, 14 + eHeading.Length);

			eFlow = new DtdElement[eInline.Length + eBlock.Length];
			eInline.CopyTo(eFlow, 0);
			eBlock.CopyTo(eFlow, eInline.Length);
			#endregion

			#region List Item Initialization

			list = new Dictionary<DtdElement, Atom>();
			
			list.Add(DtdElement.A, new Atom(DtdElement.A, true, eInline));
			list[DtdElement.A].AllowedContent.Remove(DtdElement.A);

			list.Add(DtdElement.ABBR, new Atom(DtdElement.ABBR, true, eInline));

			list.Add(DtdElement.ACRONYM, new Atom(DtdElement.ACRONYM, true, eInline));

			list.Add(DtdElement.ADDRESS, new Atom(DtdElement.ADDRESS, true, eInline));
			list[DtdElement.ADDRESS].AllowedContent.Add(DtdElement.P);
			
			list.Add(DtdElement.APPLET, new Atom(DtdElement.APPLET, true, eFlow));
			list[DtdElement.APPLET].AllowedContent.Add(DtdElement.PARAM);
			
			list.Add(DtdElement.AREA, new Atom(DtdElement.AREA, false));

			list.Add(DtdElement.B, new Atom(DtdElement.B, true, eInline));
			
			list.Add(DtdElement.BASE, new Atom(DtdElement.BASE, false));
			
			list.Add(DtdElement.BASEFONT, new Atom(DtdElement.BASEFONT, false));
			
			list.Add(DtdElement.BDO, new Atom(DtdElement.BDO, true, eInline));

			list.Add(DtdElement.BIG, new Atom(DtdElement.BIG, true, eInline));
			
			list.Add(DtdElement.BLOCKQUOTE, new Atom(DtdElement.BLOCKQUOTE, true, eFlow));
			
			list.Add(DtdElement.BODY, new Atom(DtdElement.BODY, false, eFlow));
			list[DtdElement.BODY].AllowedContent.Add(DtdElement.INS);
			list[DtdElement.BODY].AllowedContent.Add(DtdElement.DEL);
			
			list.Add(DtdElement.BR, new Atom(DtdElement.BR, false));
			
			list.Add(DtdElement.BUTTON, new Atom(DtdElement.BUTTON, true, eFlow));
			list[DtdElement.BUTTON].AllowedContent.Remove(DtdElement.A);
			list[DtdElement.BUTTON].AllowedContent.Remove(DtdElement.FORM);
			list[DtdElement.BUTTON].AllowedContent.Remove(DtdElement.ISINDEX);
			list[DtdElement.BUTTON].AllowedContent.Remove(DtdElement.FIELDSET);
			list[DtdElement.BUTTON].AllowedContent.Remove(DtdElement.IFRAME);
			for (int i = 0; i < eFormCtrl.Length; i++) list[DtdElement.BUTTON].AllowedContent.Remove(eFormCtrl[i]);
			
			list.Add(DtdElement.CAPTION, new Atom(DtdElement.CAPTION, true, eInline));

			list.Add(DtdElement.CDATA, new Atom(DtdElement.CDATA, false, DtdElement.CDATA, DtdElement.PCDATA));

			list.Add(DtdElement.CENTER, new Atom(DtdElement.CENTER, true, eFlow));

			list.Add(DtdElement.CITE, new Atom(DtdElement.CITE, true, eInline));

			list.Add(DtdElement.CODE, new Atom(DtdElement.CODE, true, eInline));
			
			list.Add(DtdElement.COL, new Atom(DtdElement.COL, false));

			list.Add(DtdElement.COLGROUP, new Atom(DtdElement.COLGROUP, false, DtdElement.COL));

			list.Add(DtdElement.COMMENT, new Atom(DtdElement.COMMENT, false));

			list.Add(DtdElement.DD, new Atom(DtdElement.DD, false, eInline));
			
			list.Add(DtdElement.DEL, new Atom(DtdElement.DEL, true, eFlow));

			list.Add(DtdElement.DFN, new Atom(DtdElement.DFN, true, eInline));

			list.Add(DtdElement.DIR, new Atom(DtdElement.DIR, true, DtdElement.LI));
			
			list.Add(DtdElement.DIV, new Atom(DtdElement.DIV, true, eFlow));

			list.Add(DtdElement.DL, new Atom(DtdElement.DL, true, DtdElement.DT, DtdElement.DD));

			list.Add(DtdElement.DT, new Atom(DtdElement.DT, false, eFlow));

			list.Add(DtdElement.EM, new Atom(DtdElement.EM, true, eInline));
			
			list.Add(DtdElement.FIELDSET, new Atom(DtdElement.FIELDSET, true, eFlow));
			
			list.Add(DtdElement.FONT, new Atom(DtdElement.FONT, true, eInline));
			
			list.Add(DtdElement.FORM, new Atom(DtdElement.FORM, true, eFlow));
			list[DtdElement.FORM].AllowedContent.Remove(DtdElement.FORM);
			
			list.Add(DtdElement.FRAME, new Atom(DtdElement.FRAME, false));
			
			list.Add(DtdElement.FRAMESET, new Atom(DtdElement.FRAMESET, true, DtdElement.FRAMESET, DtdElement.FRAME, DtdElement.NOFRAMES));
			
			list.Add(DtdElement.H1, new Atom(DtdElement.H1, true, eInline));

			list.Add(DtdElement.H2, new Atom(DtdElement.H2, true, eInline));

			list.Add(DtdElement.H3, new Atom(DtdElement.H3, true, eInline));

			list.Add(DtdElement.H4, new Atom(DtdElement.H4, true, eInline));

			list.Add(DtdElement.H5, new Atom(DtdElement.H5, true, eInline));

			list.Add(DtdElement.H6, new Atom(DtdElement.H6, true, eInline));
			
			list.Add(DtdElement.HEAD, new Atom(DtdElement.HEAD, false, DtdElement.SCRIPT, DtdElement.STYLE, DtdElement.PCDATA,
				DtdElement.META, DtdElement.LINK, DtdElement.OBJECT,DtdElement.TITLE, DtdElement.ISINDEX, DtdElement.BASE));
			
			list.Add(DtdElement.HR, new Atom(DtdElement.HR, false));

			list.Add(DtdElement.HTML, new Atom(DtdElement.HTML, false, DtdElement.HEAD, DtdElement.FRAMESET, DtdElement.BODY, DtdElement.PCDATA));

			list.Add(DtdElement.I, new Atom(DtdElement.I, true, eInline));
			
			list.Add(DtdElement.IFRAME, new Atom(DtdElement.IFRAME, true, eFlow));
			
			list.Add(DtdElement.IMG, new Atom(DtdElement.IMG, false));
			
			list.Add(DtdElement.INPUT, new Atom(DtdElement.INPUT, false));
			
			list.Add(DtdElement.INS, new Atom(DtdElement.INS, true, eFlow));
			
			list.Add(DtdElement.ISINDEX, new Atom(DtdElement.ISINDEX, false));

			list.Add(DtdElement.KBD, new Atom(DtdElement.KBD, true, eInline));
			
			list.Add(DtdElement.LABEL, new Atom(DtdElement.LABEL, true, eInline));
			list[DtdElement.LABEL].AllowedContent.Remove(DtdElement.LABEL);
			
			list.Add(DtdElement.LEGEND, new Atom(DtdElement.LEGEND, true, eInline));

			list.Add(DtdElement.LI, new Atom(DtdElement.LI, false, eFlow));
			
			list.Add(DtdElement.LINK, new Atom(DtdElement.LINK, false));
			
			list.Add(DtdElement.MAP, new Atom(DtdElement.MAP, true, eBlock));
			list[DtdElement.MAP].AllowedContent.Add(DtdElement.AREA);

			list.Add(DtdElement.MENU, new Atom(DtdElement.MENU, true, DtdElement.LI));
			
			list.Add(DtdElement.META, new Atom(DtdElement.META, false));
			
			list.Add(DtdElement.NOFRAMES, new Atom(DtdElement.NOFRAMES, true, eFlow));
			
			list.Add(DtdElement.NOSCRIPT, new Atom(DtdElement.NOSCRIPT, true, eFlow));

			list.Add(DtdElement.OBJECT, new Atom(DtdElement.OBJECT, true, eFlow));
			list[DtdElement.OBJECT].AllowedContent.Add(DtdElement.PARAM);

			list.Add(DtdElement.OL, new Atom(DtdElement.OL, true, DtdElement.LI));

			list.Add(DtdElement.OPTGROUP, new Atom(DtdElement.OPTGROUP, true, DtdElement.OPTION));

			list.Add(DtdElement.OPTION, new Atom(DtdElement.OPTION, false, DtdElement.PCDATA));
			
			list.Add(DtdElement.P, new Atom(DtdElement.P, false, eInline));
			
			list.Add(DtdElement.PARAM, new Atom(DtdElement.PARAM, false));

			list.Add(DtdElement.PCDATA, new Atom(DtdElement.PCDATA, false,	DtdElement.CDATA, DtdElement.PCDATA));

			list.Add(DtdElement.PRE, new Atom(DtdElement.PRE, true, eInline)); //then remove the DTD %pre.exclusion
			list[DtdElement.PRE].AllowedContent.Remove(DtdElement.IMG);
			list[DtdElement.PRE].AllowedContent.Remove(DtdElement.OBJECT);
			list[DtdElement.PRE].AllowedContent.Remove(DtdElement.APPLET);
			list[DtdElement.PRE].AllowedContent.Remove(DtdElement.BIG);
			list[DtdElement.PRE].AllowedContent.Remove(DtdElement.SMALL);
			list[DtdElement.PRE].AllowedContent.Remove(DtdElement.SUB);
			list[DtdElement.PRE].AllowedContent.Remove(DtdElement.SUP);
			list[DtdElement.PRE].AllowedContent.Remove(DtdElement.FONT);
			list[DtdElement.PRE].AllowedContent.Remove(DtdElement.BASEFONT);
			
			list.Add(DtdElement.Q, new Atom(DtdElement.Q, true, eInline));

			list.Add(DtdElement.S, new Atom(DtdElement.S, true, eInline));

			list.Add(DtdElement.SAMP, new Atom(DtdElement.SAMP, true, eInline));
			
			list.Add(DtdElement.SCRIPT, new Atom(DtdElement.SCRIPT, true, DtdElement.CDATA));

			list.Add(DtdElement.SELECT, new Atom(DtdElement.SELECT, true, DtdElement.OPTGROUP, DtdElement.OPTION));

			list.Add(DtdElement.SMALL, new Atom(DtdElement.SMALL, true, eInline));
			
			list.Add(DtdElement.SPAN, new Atom(DtdElement.SPAN, true, eInline));

			list.Add(DtdElement.STRIKE, new Atom(DtdElement.STRIKE, true, eInline));

			list.Add(DtdElement.STRONG, new Atom(DtdElement.STRONG, true, eInline));
			
			list.Add(DtdElement.STYLE, new Atom(DtdElement.STYLE, true, DtdElement.CDATA));
			
			list.Add(DtdElement.SUB, new Atom(DtdElement.SUB, true, eInline));
			
			list.Add(DtdElement.SUP, new Atom(DtdElement.SUP, true, eInline));
			
			list.Add(DtdElement.TABLE, new Atom(DtdElement.TABLE, true, DtdElement.CAPTION, DtdElement.COL, DtdElement.COLGROUP, DtdElement.THEAD, DtdElement.TFOOT, DtdElement.TBODY, DtdElement.TR));

			list.Add(DtdElement.TBODY, new Atom(DtdElement.TBODY, false, DtdElement.TR));

			list.Add(DtdElement.TD, new Atom(DtdElement.TD, false, eFlow));

			list.Add(DtdElement.TEXTAREA, new Atom(DtdElement.TEXTAREA, true, DtdElement.PCDATA));

			list.Add(DtdElement.TFOOT, new Atom(DtdElement.TFOOT, false, DtdElement.TR));

			list.Add(DtdElement.TH, new Atom(DtdElement.TH, false, eFlow));

			list.Add(DtdElement.THEAD, new Atom(DtdElement.THEAD, false, DtdElement.TR));

			list.Add(DtdElement.TITLE, new Atom(DtdElement.TITLE, true, DtdElement.PCDATA));

			list.Add(DtdElement.TR, new Atom(DtdElement.TR, false, DtdElement.TH, DtdElement.TD));

			list.Add(DtdElement.TT, new Atom(DtdElement.TT, true, eInline));

			list.Add(DtdElement.U, new Atom(DtdElement.U, true, eInline));
			
			list.Add(DtdElement.UL, new Atom(DtdElement.UL, true, DtdElement.LI));

			list.Add(DtdElement.VAR, new Atom(DtdElement.VAR, true, eInline));

			#endregion
		}

		public static Dictionary<DtdElement, Atom> List { get { return list; } }
		private static readonly Dictionary<DtdElement, Atom> list;

		#region Static Arrarys of DtdElements for AllowedContent
		private static readonly DtdElement[] eFontStyle = new DtdElement[8] { 
			DtdElement.TT,
			DtdElement.I,
			DtdElement.B,
			DtdElement.U,
			DtdElement.S,
			DtdElement.STRIKE,
			DtdElement.BIG,
			DtdElement.SMALL
		};
		
		private static readonly DtdElement[] ePhrase = new DtdElement[10] { 
			DtdElement.EM,
			DtdElement.STRONG,
			DtdElement.DFN,
			DtdElement.CODE,
			DtdElement.SAMP,
			DtdElement.KBD,
			DtdElement.VAR,
			DtdElement.CITE,
			DtdElement.ABBR,
			DtdElement.ACRONYM
		};

		private static readonly DtdElement[] eSpecial = new DtdElement[15] { 
		   DtdElement.A,
		   DtdElement.IMG,
		   DtdElement.APPLET,
		   DtdElement.OBJECT,
		   DtdElement.FONT,
		   DtdElement.BASEFONT,
		   DtdElement.BR,
		   DtdElement.SCRIPT,
		   DtdElement.MAP,
		   DtdElement.Q,
		   DtdElement.SUB,
		   DtdElement.SUP,
		   DtdElement.SPAN,
		   DtdElement.BDO,
		   DtdElement.IFRAME
		};

		private static readonly DtdElement[] eFormCtrl = new DtdElement[5] { 
			DtdElement.INPUT,
			DtdElement.SELECT,
			DtdElement.TEXTAREA,
			DtdElement.LABEL,
			DtdElement.BUTTON
		};

		private static readonly DtdElement[] eInline;

		private static readonly DtdElement[] eHeading = new DtdElement[6] { 
			DtdElement.H1,
			DtdElement.H2,
			DtdElement.H3,
			DtdElement.H4,
			DtdElement.H5,
			DtdElement.H6
		};

		private static readonly DtdElement[] eList = new DtdElement[4] { 
			DtdElement.UL,
			DtdElement.OL,
			DtdElement.DIR,
			DtdElement.MENU
		};

		private static readonly DtdElement[] eBlock;

		private static readonly DtdElement[] eFlow;
		#endregion
	}

	/// <summary>
	/// Enumeration of HTML tag elements.
	/// <para>Derived from the HTML 4.01 Transitional DTD found at http://www.w3.org/TR/html4/sgml/loosedtd.html.</para> 
	/// <para>This document includes elements and their attributes that will be phased out but continue to be used in real HTML.</para>
	/// <para>The list was double checked by comparing with http://www.htmlhelp.org/reference/html40/alist.html.</para>
	/// </summary>
	public enum DtdElement : byte
	{
		A,
		ABBR,
		ACRONYM,
		ADDRESS,
		APPLET,
		AREA,
		B,
		BASE,
		BASEFONT,
		BDO,
		BIG,
		BLOCKQUOTE,
		BODY,
		BR,
		BUTTON,
		CAPTION,
		/// <summary>
		/// This is a special case for all text between SCRIPT and other CDATA only tags.
		/// </summary>
		CDATA,
		CENTER,
		CITE,
		CODE,
		COL,
		COLGROUP,
		/// <summary>
		/// This is a special case for distinguishing comments from plain PCDATA or CDATA text
		/// between some other element.
		/// </summary>
		COMMENT,
		DD,
		DEL,
		DFN,
		DIR,
		DIV,
		DL,
		DT,
		EM,
		FIELDSET,
		FONT,
		FORM,
		FRAME,
		FRAMESET,
		H1,
		H2,
		H3,
		H4,
		H5,
		H6,
		HEAD,
		HR,
		HTML,
		I,
		IFRAME,
		IMG,
		INPUT,
		INS,
		ISINDEX,
		KBD,
		LABEL,
		LEGEND,
		LI,
		LINK,
		MAP,
		MENU,
		META,
		NOFRAMES,
		NOSCRIPT,
		OBJECT,
		OL,
		OPTGROUP,
		OPTION,
		P,
		PARAM,
		/// <summary>
		/// PCDATA is a special case and departs from the DTS. It signifies HTML text content not enclosed in brackets. 
		/// This text may contain external document entities that begin with an ampersand and should be Html Decoded to be sure 
		/// the text is in human readable form.
		/// </summary>
		PCDATA,
		PRE,
		Q,
		S,
		SAMP,
		SCRIPT,
		SELECT,
		SMALL,
		SPAN,
		STRIKE,
		STRONG,
		STYLE,
		SUB,
		SUP,
		TABLE,
		TBODY,
		TD,
		TEXTAREA,
		TFOOT,
		TH,
		THEAD,
		TITLE,
		TR,
		TT,
		U,
		UL,
		VAR
	} 
}
